Principal | Gráficos 3D | Gráficos 2D | Fractales | Math | Códigos | Tutoriales | Links
La posibilidad de incursionar en el terreno de las representaciones tridimensionales produce
a priori la impresion de una excesiva dificultad, sin embargo las bases de estas tecnicas, son
sencillas, y se pueden obtener resulatados sorprendentes con bastante poco esfuerzo.
Utilizo el compilador BorlandC++ 3.1 y las librerias BGI, las limitaciones son la cantidad de
vertices y poligonos que puede tener el modelo, ya que se trata de un compilador de 16 Bits
y estoy limitado por los 64K de datos que se puede utilizar ya que no utilizo asignacion dinamica
de memoria, por no complicar el código.
La imagen de abajo se realizó con el programa presentado aqui, se trata de un toro compuesto
por 512 vertices y 1024 poligonos.
Para la visualización de un objeto 3D, se requieren 3 pasos:
Mi objeto esta compuesto por polígonos de tres lados, y cada poligono tiene 3 vértices cuyas coordenadas son (x,y,z).
Número máximo de vértices que puede tener el objeto #define NUM_VERTICES Cantidad máxima de polígonos que tiene el objeto #define NUM_FACES Definición de un vértice typedef struct VTX { double x, y, z; }; Definición de un polígono typedef struct TRI { int a, b, c; }; Estructura para el ordenamiento qsort, donde para cada polígono almaceno la cordenada z y el indice del polígono d=coordenada z del poligono, index=indice del poligono typedef struct LSTRI { int d; int index; }; Tabla que contiene los vértices originales VTX points[NUM_VERTICES]; Tabla con los vértices transformados VTX outpoints[NUM_VERTICES]; Tabla que contiene los polígonos TRI faces[NUM_FACES]; Tabla de polígonos para aplicar el ordenamiento qsort LSTRI ListFaces[NUM_FACES]; Número de vértices, y polígonos del objeto int vertices, poligonos;
El sistema de coordenadas tridimensional utilizado es el llamado de la mano izquierda |
La matriz identidad se representa como matriz_identidad :
1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1
El escalado consiste en multiplicar las coordenadas del objeto por una constante,
para modificar su tamaño. Se describe el escalamiento cuando el punto fijo es el
origen. Para obtener un escalamiento con un punto fijo arbitrario, este se debe
trasladar al origen, escalar el objeto, y despues realizar el inverso de la traslacion
original.
La matriz de escalamiento con factores de escala Ex, Ey, Ez en
las direcciones x, y, z respectivamente, esta dada por la matriz
Es(Ex, Ey, Ez) :
Ex 0 0 0 0 Ey 0 0 0 0 Ez 0 0 0 0 1
La inversa de un escalamiento se obtiene usando los reciprocos de los factores de escala : 1/Ex, 1/Ey, 1/Ez.
La rotación de un angulo a sobre el eje x se representa por medio de la matriz Rx(a) :
1 0 0 0 0 cos(a) sin(a) 0 0 -sin(a) cos(a) 0 0 0 0 1
Código en C
void matriz_rotacion_x(MATRIZ *m, double angulo) { double tmpsin, tmpcos; tmpsin=sin(angulo); tmpcos=cos(angulo); *m = matriz_identidad; m->v[1][1] = tmpcos; m->v[1][2] = tmpsin; m->v[2][1] = -tmpsin; m->v[2][2] = tmpcos; }
Ejemplo: matriz_rotacion_x(&Rx, 0.7)
Esto crea la matriz de rotación alrededor del eje X, donde Rx
es una matriz 4x4 y 0.7 es el angulo de rotación, en radianes.
La rotación de un punto alrededor del eje Y se realiza en un plano paralelo al plano x, z. El valor de la coordenada Y no cambia. La matriz de rotacion es Ry(a) :
cos(a) 0 -sin(a) 0 0 1 0 0 sin(a) 0 cos(a) 0 0 0 0 1
Código en C.
void matriz_rotacion_y(MATRIZ *m, double angulo) { double tmpsin, tmpcos; tmpsin=sin(angulo); tmpcos=cos(angulo); *m = matriz_identidad; m->v[0][0] = tmpcos; m->v[0][2] = -tmpsin; m->v[2][0] = tmpsin; m->v[2][2] = tmpcos; }
Ejemplo : matriz_rotacion_y(&Ry, 0.7)
donde Ry es la matriz de rotación 4x4, y 0.7 es el angulo de
rotacióon.
La rotación de un angulo a sobre el eje Z se representa por medio de la matriz Rz(a) :
cos(a) sin(a) 0 0 -sin(a) cos(a) 0 0 0 0 1 0 0 0 0 1
Código en C
void matriz_rotacion_z(MATRIZ *m, double angulo) { double tmpsin, tmpcos; tmpsin=sin(angulo); tmpcos=cos(angulo); *m = matriz_identidad; m->v[0][0] = tmpcos; m->v[0][1] = tmpsin; m->v[1][0] = -tmpsin; m->v[1][1] = tmpcos; }
Ejemplo : matriz_rotacion_z(&Rz, 0.7)
donde Rz es la matriz de 4x4, y 0.7 es el angulo de rotación en torno al eje Z.
La matriz asociada a la traslación es la siguiente Tr(Tx, Ty, Tz) :
1 0 0 0 0 1 0 0 0 0 1 0 Tx Ty Tz 1
Código en C
void matriz_traslacion(MATRIZ *m, double Tx, double Ty, double Tz) { *m = matriz_identidad; m->v[3][0] = Tx; m->v[3][1] = Ty; m->v[3][2] = Tz; }
Ejemplo : matriz_traslacion(&Tr, 0.0, 0.0, -350.0)
donde Tr es la matriz de traslación 4x4, y -100.0 es el
desplazamiento en el eje Z.
La matriz de traslación inversa se obtiene invirtiendo el signo de los
valores de Tx, Ty, Tz. Es decir Tr(-Tx,-Ty,-Tz)
Hay que tener en cuenta que no es lo mismo rotar y luego trasladar, que trasladar y luego rotar: Si se rota el objeto y luego se traslada, aparecerá el objeto (rotado sobre si mismo) en el punto correspondiente a la traslación, en cambio, si primero se traslada y luego se rota, se obtiene algo totalmente distinto, pues el objeto no rota sobre si mismo, sino que lo hace respecto al origen de coordenadas de la escena. Esto es debido a que el eje sobre el que se realiza la rotación pasa por el origen de coordenadas, que será el origen relativo del objeto o el global de las escena. Si se traslada el objeto, el origen de coordenadas correspondera a un punto distinto del objeto.
Todo esta listo para construir la matriz de transformación, para ello
utilizo las matrices Rx, Ry, Rz y Tr :
void matriz_multiplicacion(MATRIZ *m1, MATRIZ *m2, MATRIZ *out)
que efectua el producto de dos matrices m1, m2, de 4x4 y
almacena el resultado en una matriz out de 4x4.
temp = Rx x Ry matriz_multiplicacion(&Rx, &Ry, &temp) Rot = temp x Rz matriz_multiplicacion(&temp, &Rz, &Rot) Mat = Rot x Tr matriz_multiplicacion(&Rot, &Tr, &Mat)
donde temp, Rot y Mat son matrices de 4x4, Mat es la matriz de transformación.
Una vez construida la matriz de transformación Mat, tenemos que aplicarla a
todos los vertices del objeto, utilizamos para ello :
aplicar_matriz(&Mat, points[i].x, points[i].y, points[i].z,
&outpoints[i].x, &outpoints[i].y, &outpoints[i].z)
me multiplica cada uno de los vertices del objeto (points[]) por la matriz de
transformación Mat, el resultado es el vertice transformado
(outpoints[]).
Esta proyección es una transformación que convierte una representación tridimencional en bidimencional, para poder representarla en la pantalla, D es la distancia entre el plano de proyeccion (pantalla) y el obeservador, con valores pequenos la imagen se distorciona, con D=800, anda todo bien, persp_xoffset, persp_yoffset son las coordenadas del centro de la pantalla, por ejemplo si trabajo en el modo 640x480, persp_xoffset=320 persp_yoffset=240.
void persp_projeccion(double x, double y, double z, double *xout, double *yout) { double z1 = 1.0 / z; *xout = ((x * z1) * D) + persp_xoffset; *yout = ((y * z1) * D) + persp_yoffset; }
Ejemplo : persp_projeccion(outpoints[i].x, outpoints[i].y, outpoints[i].z,
&outpoints[i].x, &outpoints[i].y)
La salida son puntos que se pueden representar en la pantalla.
Primero realizó una clasificación por profundidad para cada polígono utilizando qsort, ordenación rapida, y usando las coordenadas z. Luego para cada uno de estos polígonos uso el algoritmo de Backface-Culling para determinar si es visible o no por el observador.
Para determinar que polígonos son visibles desde el punto de vista del observador debemos realizar los siguientes pasos :
double Hidden(VTX v1, VTX v2, VTX v3) { VTX u, v; VTX out; double xout, yout, zout; double temp; u.x = v2.x - v1.x; u.y = v2.y - v1.y; u.z = v2.z - v1.z; v.x = v3.x - v1.x; v.y = v3.y - v1.y; v.z = v3.z - v1.z; producto_vectorial(u, v, &out); temp = producto_escalar(out.x, out.y, out.z, 0.0, 0.0, -1.0); return temp; }
Solo funciona bien si el modelo consiste en un unico solido convexo (esfera), pues si hay mas de un solido, o si este no es convexo (toro), un poligono puede tapar parcialmente a otro poligono (ambos visibles), por lo que es necesario utilizar otro metodo complementario.
Consiste en trazar primero los poligonos situados más lejos. Para ello se ordenan los poligonos en función de su profundidad media, utilizo para ello el metodo de ordenacion
rapida qsort.
No siempre funciona bien, porque no siempre todos los vertices de un poligono estan mas cerca o mas lejos que todos los vertices de otro poligono. Aunque es inexacto (especialmente en poligono de gran tamaño).
Este efecto se logra pintando los poligonos con diferentes tonos de grises, para
ello utilizo el vector normal al poligono, el cual tiene que ser normalizado, es decir
dividir el vector por su modulo, para que sea unitario,y el vector de componentes xlight, ylight, zlight que son las coordenadas de la fuente de luz, este vector tambien tiene que ser normalizado, realizo el producto_escalar entre ambos vectores el valor que me da como resultado me indica la cantidad de luz que recibe
cada poligono, 0.0 intensidad minima y 1.0 maxima intensidad.
Utilizo una paleta de 256 colores, en este caso son niveles de grises 64 en total, como
el nivel de luz varia entre 0.0 y 1.0, lo escalo multiplicandolo por 256.0, que es la
cantidad de colores que tiene mi paleta.
La siguiente funcion me calcula el nivel de luz, que recibe un poligono.
int Light(VTX v1, VTX v2, VTX v3) { VTX u, v, out; double temp; u.x = v2.x - v1.x; u.y = v2.y - v1.y; u.z = v2.z - v1.z; v.x = v3.x - v1.x; v.y = v3.y - v1.y; v.z = v3.z - v1.z; //out es el vector normal al poligono producto_vectorial(u, v, &out); vector_normalizado(&out.x, &out.y, &out.z); vector_normalizado(&xlight, &ylight, &zlight); //El resultado de todas estas operaciones dar un valor comprendido //entre 0.0 (m¡nima luz) y 1.0f (m xima luz) temp = producto_escalar(out.x, out.y, out.z, xlight, ylight, zlight); temp = 256.0*temp; if (temp<0.0) temp=0.0; else if (temp>255.0) temp=255.0; return (int) (temp); }
Se define el objeto a visualizar en un archivo de texto (toro.dat), el cual
es leido desde el programa, esta compuesto por 512 vertices y
1024 poligonos de tres vertices. La representación es en modo
Flat Shading.
Utilizo las librerias BGI de BorlandC++3.1, con el comando fillpoly()
dibujo los poligonos.
Estructura del archivo de texto toro.dat que contiene los datos del objeto.
512 (es el número de vértices que tiene el objeto) -0.000003 75.000000 0.000000 (coordenadas de los vertices) -0.000003 73.096985 9.567086 -0.000003 67.677666 17.677670 .......... .......... ......... 1024 (es el numero de poligonos del objeto) 0 17 16 (indice de los vertices que forman cada poligono) 0 1 17 1 18 17 . . .....
Imagen generada por el programa, utilizando una paleta de 64 tonos de grises.
Codigo Fuente Artic1.zip
Archivo que contiene los datos del objeto 3d.zip
Archivo que contiene los datos del objeto toro.zip
Archivos svga256.bgi y svga256.h, necesarios para la compilacion svga256.zip
/* * Ejemplo de transformaciones 3D utilzando matrices 4x4 * * Este programa carga la geometria del objeto, es decir * las coordenadas de los vertices, y las caras desde una * archivo de texto (toro.dat), y le aplica las transformaciones * necesarias para lograr una proyeccion en perspectiva, para * ello utiliza matrices 4x4. * La representacion es en modo flat, son eliminadas las caras * que no son visbles. * * El codigo no esta optimizdo, ya que la finalidad de este * programa es mostrar el uso de las matrices de transformacion. * Fue compilado con BorlandC++3.1, utiliza las librerias BGI * * www.oocities.org/valcoey/index.html * valcoey@hotmail.com * Ramiro Alcocer * 12/01/2001 */ #include <graphics.h> #include <stdlib.h> #include <math.h> #include <stdio.h> #include <conio.h> #include <dos.h> #include "svga256.h" #define NUM_VERTICES 512 #define NUM_FACES 1024 typedef struct MATRIZ { double v[4][4]; }; typedef struct VTX { double x, y, z; }; typedef struct TRI { int a, b, c; }; typedef struct LSTRI { int d; int index; }; VTX points[NUM_VERTICES]; VTX outpoints[NUM_VERTICES]; TRI faces[NUM_FACES]; LSTRI ListFaces[NUM_FACES]; int vertices, poligonos; //posicion de la fuente de luz double xlight=1.0, ylight=-1.0, zlight=1.0; unsigned char paleta[256][3]; MATRIZ matriz_identidad = { { { 1.0, 0.0, 0.0, 0.0}, { 0.0, 1.0, 0.0, 0.0}, { 0.0, 0.0, 1.0, 0.0}, { 0.0, 0.0, 0.0, 1.0} } }; double persp_xoffset; double persp_yoffset; double D; int huge deteccion() { int modo; clrscr(); printf("Selecciona un modo de video...\n"); printf(" 0) 320x200x256 VGA\n"); printf(" 1) 640x400x256 SVGA\n"); printf(" 2) 640x480x256 SVGA\n"); printf(" 3) 800x600x256 SVGA\n"); printf(" 4) 1024x768x256 SVGA\n\n>"); scanf("%d", &modo); return modo; } void aplicar_matriz(MATRIZ *m, double x, double y, double z, double *xout, double *yout, double *zout) { *xout = x*m->v[0][0] + y*m->v[1][0] + z*m->v[2][0] + 1.0*m->v[3][0]; *yout = x*m->v[0][1] + y*m->v[1][1] + z*m->v[2][1] + 1.0*m->v[3][1]; *zout = x*m->v[0][2] + y*m->v[1][2] + z*m->v[2][2] + 1.0*m->v[3][2]; } void matriz_multiplicacion(MATRIZ *m1, MATRIZ *m2, MATRIZ *out) { int i, j, k; for (i=0; i<4; i++) { for (j=0; j<4; j++) { out->v[i][j] = 0.0; for (k=0; k<4; k++) out->v[i][j] = out->v[i][j] + m1->v[i][k]*m2->v[k][j]; } } } void matriz_traslacion(MATRIZ *m, double Tx, double Ty, double Tz) { *m = matriz_identidad; m->v[3][0] = Tx; m->v[3][1] = Ty; m->v[3][2] = Tz; } void matriz_escalamiento(MATRIZ *m, double Ex, double Ey, double Ez) { *m = matriz_identidad; m->v[0][0] = Ex; m->v[1][1] = Ey; m->v[2][2] = Ez; } void matriz_rotacion_x(MATRIZ *m, double angulo) { double tmpsin, tmpcos; tmpsin=sin(angulo); tmpcos=cos(angulo); *m = matriz_identidad; m->v[1][1] = tmpcos; m->v[1][2] = tmpsin; m->v[2][1] = -tmpsin; m->v[2][2] = tmpcos; } void matriz_rotacion_y(MATRIZ *m, double angulo) { double tmpsin, tmpcos; tmpsin=sin(angulo); tmpcos=cos(angulo); *m = matriz_identidad; m->v[0][0] = tmpcos; m->v[0][2] = -tmpsin; m->v[2][0] = tmpsin; m->v[2][2] = tmpcos; } void matriz_rotacion_z(MATRIZ *m, double angulo) { double tmpsin, tmpcos; tmpsin=sin(angulo); tmpcos=cos(angulo); *m = matriz_identidad; m->v[0][0] = tmpcos; m->v[0][1] = tmpsin; m->v[1][0] = -tmpsin; m->v[1][1] = tmpcos; } void persp_projeccion(double x, double y, double z, double *xout, double *yout) { double z1 = 1.0 / z; *xout = ((x * z1) * D) + persp_xoffset; *yout = ((y * z1) * D) + persp_yoffset; } void set_projeccion_viewport(int w, int h) { D = 800; persp_xoffset = w/2; persp_yoffset = h/2; } void ver_matriz(MATRIZ *m) { printf("%lf %lf %lf %lf\n", m->v[0][0], m->v[0][1], m->v[0][2], m->v[0][3]); printf("%lf %lf %lf %lf\n", m->v[1][0], m->v[1][1], m->v[1][2], m->v[1][3]); printf("%lf %lf %lf %lf\n", m->v[2][0], m->v[2][1], m->v[2][2], m->v[2][3]); printf("%lf %lf %lf %lf\n", m->v[3][0], m->v[3][1], m->v[3][2], m->v[3][3]); } double producto_escalar(double x1, double y1, double z1, double x2, double y2, double z2) { return ((x1 * x2) + (y1 * y2) + (z1 * z2)); } void producto_vectorial(VTX v1, VTX v2, VTX *out) { out->x = (v1.y * v2.z) - (v1.z * v2.y); out->y = (v1.z * v2.x) - (v1.x * v2.z); out->z = (v1.x * v2.y) - (v1.y * v2.x); } double modulo_vector(double x, double y, double z) { return sqrt(x*x + y*y + z*z); } void vector_normalizado(double *x, double *y, double *z) { double length = 1.0 / modulo_vector(*x, *y, *z); *x *= length; *y *= length; *z *= length; } //Esta funcion permite conocer si un poligono esta de cara //o de espaldas al observador, segun devuelva un valor mayor //o menor que cero double Hidden(VTX v1, VTX v2, VTX v3) { VTX u, v; VTX out; double xout, yout, zout; double temp; u.x = v2.x - v1.x; u.y = v2.y - v1.y; u.z = v2.z - v1.z; v.x = v3.x - v1.x; v.y = v3.y - v1.y; v.z = v3.z - v1.z; producto_vectorial(u, v, &out); temp = producto_escalar(out.x, out.y, out.z, 0.0, 0.0, -1.0); return temp; } //funcion usada para calcular el nivel de intensidad de la //luz que recibe un poligono int Light(VTX v1, VTX v2, VTX v3) { VTX u, v, out; double temp; u.x = v2.x - v1.x; u.y = v2.y - v1.y; u.z = v2.z - v1.z; v.x = v3.x - v1.x; v.y = v3.y - v1.y; v.z = v3.z - v1.z; //out es el vector normal al poligono producto_vectorial(u, v, &out); vector_normalizado(&out.x, &out.y, &out.z); vector_normalizado(&xlight, &ylight, &zlight); //El resultado de todas estas operaciones dar un valor comprendido //entre 0.0 (m¡nima luz) y 1.0f (m xima luz) temp = producto_escalar(out.x, out.y, out.z, xlight, ylight, zlight); temp = 256.0*temp; if (temp<0.0) temp=0.0; else if (temp>255.0) temp=255.0; return (int) (temp); } //me dibuja un poligono solido, el color lo determino //de acuerdo al nivel de luz que recibe. void DibujarPoligono(int num) { int A, B, C; int poly[6]; int i, color; i=ListFaces[num].index; A=faces[i].a; B=faces[i].b; C=faces[i].c; //verifico si el poligono es visible o no if (Hidden(outpoints[A], outpoints[B], outpoints[C])<0.0) { //se llama a la funci¢n que calcula la luz de un pol¡gono color = Light(outpoints[A], outpoints[B], outpoints[C]); poly[0]=(int) outpoints[A].x; poly[1]=(int) outpoints[A].y; poly[2]=(int) outpoints[B].x; poly[3]=(int) outpoints[B].y; poly[4]=(int) outpoints[C].x; poly[5]=(int) outpoints[C].y; setfillstyle(SOLID_FILL, color); setcolor(color); fillpoly(3, poly); } } int comparar(const void * e1, const void * e2) { int d1, d2; LSTRI *q1 = (LSTRI *)e1; LSTRI *q2 = (LSTRI *)e2; d1=q1->d; d2=q2->d; return (d2-d1); } void setvgapalette256(DacPalette256 *PalBuf) { struct REGPACK reg; reg.r_ax = 0x1012; reg.r_bx = 0; reg.r_cx = 256; reg.r_es = FP_SEG(PalBuf); reg.r_dx = FP_OFF(PalBuf); intr(0x10,®); } void LeerObjeto(char *nombre) { int i; FILE *in = fopen(nombre, "r"); if (!in) return; fscanf(in, "%d\n", &vertices); //printf("%d\n", vertices); for (i = 0; i<vertices; i++) { fscanf(in, "%lf %lf %lf\n", &points[i].x, &points[i].y, &points[i].z); //printf("%lf %lf %lf\n", points[i].x, points[i].y, points[i].z); }; fscanf(in, "%d\n", &poligonos); //printf("%d\n", poligonos); for (i = 0; i<poligonos; i++) { fscanf(in, "%d %d %d\n", &faces[i].a, &faces[i].b, &faces[i].c); //printf("%d %d %d\n", faces[i].a, faces[i].b, faces[i].c); } //printf("Presionar una tecla para continuar..."); //getch(); fclose(in); } int main(void) { MATRIZ Rx, Ry, Rz, Rot; MATRIZ Tr, Mat; MATRIZ temp; int i; //Carga el modelo en memoria LeerObjeto("toro.dat"); //Inicializa el modo grafico int driver = DETECT, modo = DETECT; installuserdriver("Svga256", deteccion); initgraph(&driver, &modo, ""); for (i=0; i<256; i++) { paleta[i][0]=i/4; paleta[i][1]=i/4; paleta[i][2]=i/4; } //me cambia la paleta de colores a una escala de 64 tonos de grises setvgapalette256(&paleta); for (i=0; i<256; i++) { setcolor(i); line(i,0,i,20); }; set_projeccion_viewport(getmaxx(), getmaxy()); //Inicializa las matrices de rotacion y //traslacion. matriz_rotacion_x(&Rx, -0.785); matriz_rotacion_y(&Ry, 0.523); matriz_rotacion_z(&Rz, 0.0); matriz_traslacion(&Tr, 0.0, 0.0, -350.0); //multiplica las matrices //Mat=Rx*Ry*Rz*Tr (matriz de transformacion) matriz_multiplicacion(&Rx, &Ry, &temp); matriz_multiplicacion(&temp, &Rz, &Rot); matriz_multiplicacion(&Rot, &Tr, &Mat); //aplica la matriz de transformacion a cada uno //de los vertices del modelo. for (i=0; i<vertices; i++) { aplicar_matriz(&Mat, points[i].x, points[i].y, points[i].z, &outpoints[i].x, &outpoints[i].y, &outpoints[i].z); persp_projeccion(outpoints[i].x, outpoints[i].y, outpoints[i].z, &outpoints[i].x, &outpoints[i].y); }; //construyo una tabla con las coordenadas z, de cada uno //de los poligonos. for (i=0; i<poligonos; i++) { ListFaces[i].d = (int) fabs(outpoints[faces[i].a].z + outpoints[faces[i].b].z + outpoints[faces[i].c].z); ListFaces[i].index=i; } //ordeno los poligonos de acuerdo a z. qsort(ListFaces, poligonos, sizeof(ListFaces[0]), comparar); //dibujo los poligonos for (i=0; i<poligonos; i++) DibujarPoligono(i); getch(); closegraph(); return 0; } |
valcoey@hotmail.com
Ramiro Alcocer, 2001
Principal | Gráficos 3D | Gráficos 2D | Fractales | Math | Códigos | Tutoriales | Links